<?php
/**
 * 抓取蜘蛛
 *
 * 配置示例：
 * 
    // 抓取器
    'grabber' => array(
        'engine' => 'curl',
        'option' => array(
           'cache' => array(
                'engine' => 'file',
                'option' => array('cache_dir' => V::config('tmp_path') . '/grabber')
                ),
            'cache_time' => 600,
            'user_agent' => 'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1',
        ),
    ),
 */

class Spider {

    /**
     * grabber 实例
     * @var type
     */
    private $_grabber = null;

    /**
     * 获取 grabber 对象
     * @param <type> $engine
     * @param <type> $option
     * @return Spider_Grabber_Abstract
     */
	public static function getGrabber($engine, $option = array()) {

        try {
            return Spider_Grabber::factory($engine, $option);
        } catch (Exception $e) {
            throw new Spider_Exception('init grabber error:'.$e->getMessage(), Spider_Exception::E_SPIDER_INIT_ERROR);
        }

        return null;
    }

    /**
     * 构造函数
     * @param type $option
     */
    public function __construct($option = array()) {

        if (!empty($option['grabber'])) {
            $this->_grabber = self::getGrabber($option['grabber']['engine'], $option['grabber']['option']);
        }
    }

    /**
     * 获取默认的 grabber 对象
     */
    private function _getGrabber() {

        // 用默认配置初始化
        if (null === $this->_grabber) {
            $config = V::config('spider.grabber', array());
            if (empty($config['engine'])) {
                throw new Spider_Grabber_Exception('Not config spider grabber');
            }
            $this->_grabber = self::getGrabber($config['engine'], $config['option']);
        }

        return $this->_grabber;
    }

    /**
     * 解析内容里的 a 链接
     * @param <type> $html
     * @param <type> $baseUrl
     * @param <type> $filterCallback
     */
    public static function parseUrls($html, $baseUrl = '', $filterCallback = null) {

        $links = $matches = array();
        if (preg_match_all("'<\s*a\s.*?href\s*=\s*([\"\'])?(?(1) (.*?)\\1 | ([^\s\>]+))'isx", $html, $matches)) {
            $links = self::_cookUrls($matches[2] + $matches[3], $baseUrl, $filterCallback);
        }

        return $links;
    }

    /**
     * 解析内容里的 img src
     * @param <type> $html
     * @param <type> $baseUrl
     * @param <type> $filterCallback
     */
    public static function parseImgs($html, $baseUrl = '', $filterCallback = null) {

        $links = $matches = array();
        if (preg_match_all("'<\s*img\s.*?src\s*=\s*([\"\'])?(?(1) (.*?)\\1 | ([^\s\>]+))'isx", $html, $matches)) {
            $links = self::_cookUrls($matches[2] + $matches[3], $baseUrl, $filterCallback);
        }

        return $links;
    }

    /**
     * 处理匹配到的 url
     * @param <type> $links
     * @param <type> $baseUrl
     * @param <type> $filterCallback
     * @return <type>
     */
    private static function _cookUrls($links, $baseUrl = '', $filterCallback = null) {

        $match = array();
        if ($baseUrl && preg_match('/(^[hftpsmailo]+\:\/\/[^\/]+)(\/|$)/i', $baseUrl, $match)) {
            $rootUrl = $match[1];
            $relativeUrl = preg_replace('/[\?\#].*$/', '', $baseUrl);
            $protocol = preg_replace('/\:.*$/', '', $baseUrl);
            $links = array_unique($links);
            foreach ($links as $key => $link) {
                $link = rtrim($link, '# ');
                if (strpos($link, 'javascript:') === 0) {
                    $link = '';
                }

                // 不是完整链接
                if (!preg_match('/^[hftpsmailo]+\:\/\/[^\/]+(\/|$)/i', $link, $match)) {
                    if (!$link) {
                        $link = $baseUrl;
                    } elseif ($link{0} == '/') {
                        $link = $link{1} == '/' ? $protocol.':'.$link : $rootUrl.$link;
                    } elseif ($link{0} == '?' || $link{0} == '#') {
                        $link = $relativeUrl . $link;
                    } else {
                        $link = $relativeUrl
                            . (substr($link, -1) == '/' ? '' : '/../')
                            . $link;
                    }

                    // 相对链接处理
                    $link = str_replace('/./', '/', $link);
                    if (strpos($link, '/../') !== false) {
                        $parts = explode('/', $link);
                        $arr = array();
                        foreach ($parts as $part) {
                            if ($part == '..') {
                                array_pop($arr);
                            } else {
                                array_push($arr, $part);
                            }
                        }
                        $link = implode('/', $arr);
                    }
                }
                $links[$key] = $link;
            }
        }

        if ($filterCallback) {
            $links = array_filter($links, $filterCallback);
        }
        $links = array_filter($links);
        return array_unique($links);
    }

    /**
     * 抓取指定链接内容
     * @param <type> $url
     * @param <type> $cookie
     * @param <type> $httpHeader
     * @param <type> $ignoreCache
     * @return <type>
     */
    public function fetchSource($url, $cookie = null, $httpHeader = null, $ignoreCache = false) {

        try {
            $grabber = $this->_getGrabber();
            return $grabber->fetchSource($url, $cookie, $httpHeader, $ignoreCache);
        } catch (Exception $e) {
            throw new Spider_Grabber_Exception('抓取链接内容失败：'.$e->getMessage(), Spider_Exception::E_SPIDER_GRAB_ERROR);
        }
    }

    /**
     * 抓取指定页面里的链接
     * @param <type> $url
     * @param <type> $cookie
     * @param <type> $httpHeader
     * @param <type> $ignoreCache
     * @return <type>
     */
    public function fetchLinks($url, $cookie = null, $httpHeader = null, $ignoreCache = false) {

        return self::parseUrls($this->fetchSource($url, $cookie, $httpHeader, $ignoreCache), $url);
    }

    /**
     * 抓取指定页面里的图片
     * @param <type> $url
     * @param <type> $cookie
     * @param <type> $httpHeader
     * @param <type> $ignoreCache
     * @return <type>
     */
    public function fetchImgs($url, $cookie = null, $httpHeader = null, $ignoreCache = false) {

        return self::parseImgs($this->fetchSource($url, $cookie, $httpHeader, $ignoreCache), $url);
    }

    /**
     * 从页面里获取到实体
     * @param <type> $entity
     * @param <type> $url
     * @param <type> $sourceCharset 源内容字符编码
     * @param <type> $cookie
     * @param <type> $httpHeader
     * @param <type> $ignoreCache
     * @return <type>
     */
    public function fetchEntity($entity, $url, $sourceCharset = 'UTF-8', $cookie = null, $httpHeader = null, $ignoreCache = false) {

        $html = $this->fetchSource($url, $cookie, $httpHeader, $ignoreCache);
        if (!$html) {
            return array();
        }

        try {
            $parser = new Spider_DomParser($html, $sourceCharset);
            if (!($entity instanceof Spider_DomParser_Entity)) {
                $entity = new Spider_DomParser_Entity($entity);
            }
            return $parser->fetchEntity($entity);
        } catch (Exception $e) {
            throw new Spider_Grabber_Exception('解析 dom 失败'.$e->getMessage(), Spider_Exception::E_SPIDER_PARSE_ERROR);
        }
    }
}
